﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Net;
using Microsoft.VirtualPC.Interop;
using System.Runtime.InteropServices;

namespace XP_More
{
	public partial class MainForm : Form
	{
		private VmInfo[] m_vms;
		private string m_vmFolder;
		private Settings m_appSettings = new Settings();

		public MainForm()
		{
			InitializeComponent();

            MainForm_SizeChanged(null, null);
		}

		private void MainForm_Shown(object sender, EventArgs e)
		{
			m_appSettings.Load();
			CheckUpdates();
			m_vmFolder = GetVmFolder();
			LoadVPCs();
		}

		private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
		{
			m_appSettings.Save();
		}

		private void CheckUpdates()
		{
			WebClient client = new WebClient();
			Uri uri = new Uri("http://xpmore.codeplex.com/releases");

			client.DownloadStringCompleted +=
				new DownloadStringCompletedEventHandler(DownloadsPageReceived);
			client.DownloadStringAsync(uri);
		}

		private void DownloadsPageReceived(Object sender, DownloadStringCompletedEventArgs e)
		{
			// make sure the request was not canceled and did not throw an exception
			if (e.Cancelled || e.Error != null)
			{
				return;
			}
			string pageHtml = (string)e.Result;
			// recommended release element looks like this:
			// <a id="ReleaseLinkStable0" class="SelectedRelease" 
			// title="Recommended Release: 1.0">1.0</a>
			// find the version in a dirty but quick way
			int recommendedPos = pageHtml.IndexOf("ReleaseLinkStable0");
			if (recommendedPos < 0)
				return;
			int verBegin = pageHtml.IndexOf('>', recommendedPos) + 1;
			int verEnd = pageHtml.IndexOf('<', verBegin);
			if (verEnd < verBegin || verBegin <= 0)
				return;
			string version = pageHtml.Substring(verBegin, verEnd - verBegin);
			version = version.Replace("#40;", "(").Replace("#41;", ")");
			if (version != "1.3")
			{
				updateLink.Text += version;
				updateLink.Show();
			}
		}

		private void LoadVPCs()
		{
			if (m_vmFolder.Length == 0)	// VM folder unrecognized...
				return;

			vpcListView.Items.Clear();
			string[] vmcxFiles = Directory.GetFiles(m_vmFolder, "*.vmcx");
			m_vms = new VmInfo[vmcxFiles.Length];
			for (int i = 0; i < vmcxFiles.Length; ++i)
			{
				m_vms[i] = new VmInfo(vmcxFiles[i]);
				vpcListView.Items.Add(m_vms[i].Name);
				vpcListView.Items[i].SubItems.Add(m_vms[i].State);
				vpcListView.Items[i].SubItems.Add(m_vms[i].Vhd);
				vpcListView.Items[i].SubItems.Add(m_vms[i].Vmc);
				vpcListView.Items[i].SubItems.Add(m_vms[i].Comment);
			}
			if (vmcxFiles.Length > 0)
			{
				vpcListView.Items[0].Selected = true;
			}
		}

		private string GetVmFolder()
		{
			string val = m_appSettings.Get("vmFolder");
			if (val.Length > 0 && Directory.Exists(val))
				return val;
			// the system doesn't provide a built-in way to get the
			// VMs folder, so some huristic should be used:

			// first, try to locate the folder under C:\Users\<user>
			// (it might have different names on different languages)
			string profilePath = 
				Environment.ExpandEnvironmentVariables("%USERPROFILE%");
			string[] subDirs = Directory.GetDirectories(profilePath);
			foreach (string subDir in subDirs)
			{
				if (IsVmFolder(subDir))
				{
					m_appSettings.Set("vmFolder", subDir);
					return subDir;
				}
			}

			// then, try to use the desktop as the start point,
			// go up one folder and bet on the "Virtual Machine" sub folder
			StringBuilder vmFolder = new StringBuilder();
			vmFolder.Append(Environment.GetFolderPath(
				 Environment.SpecialFolder.DesktopDirectory));
			if (vmFolder.ToString().EndsWith("\\"))
				vmFolder.Remove(vmFolder.Length - 1, 1);
			int userPos = vmFolder.ToString().LastIndexOf("\\");
			vmFolder.Remove(userPos + 1, vmFolder.Length - userPos - 1);
			vmFolder.Append("Virtual Machines\\");
			if (Directory.Exists(vmFolder.ToString()))
			{
				m_appSettings.Set("vmFolder", vmFolder.ToString());
				return vmFolder.ToString();
			}
			// finally, just ask the user for the location...
			MessageBox.Show("Darn, the VM folder could not be found...\n\n" +
				"You'll now be given a chance to select the folder directly.\n\n" +
				"May I ask that you send the chosen folder's path to\n" +
				"xpmore@live.com, so that the problem could be fixed?");
			FolderBrowserDialog browser = new FolderBrowserDialog();
			browser.ShowDialog();
			m_appSettings.Set("vmFolder", browser.SelectedPath);
			return browser.SelectedPath;
		}

		private bool IsVmFolder(string subDir)
		{
            Regex objIconPattern = new Regex(@"IconResource\s*=[^\n]*VMCPropertyHandler\.dll");

			string desktopIni = subDir + "\\desktop.ini";
			if (!File.Exists(desktopIni))
				return false;

			// the VM's desktop.ini contains text similar to:
			// [.ShellClassInfo]
			// LocalizedResourceName=@%SystemRoot%\system32\VMCPropertyHandler.dll,-507
			// InfoTip=@%SystemRoot%\system32\VMCPropertyHandler.dll,-508
			// IconResource=%SystemRoot%\system32\VMCPropertyHandler.dll,-1005
			string content = File.ReadAllText(desktopIni);

			// I'm optimistically assuming if VMCPropertyHandler.dll is found
			// in desktop.ini, it's the VM's folder. correct me if I'm wrong...

            // The original version of the code used to simply check for the VMCPropertyHandler
            // string in the desktop.ini but the problem with that is that links to virtual
            // machines screw up the search.  Here's the old code
            //if (content.IndexOf("VMCPropertyHandler.dll") < 0)
            //	return false;
            //else
            //  return true;

            // I modified the code to instead check for a line in the desktop.ini that starts
            // with IconResource and contains VMCPropertyHandler.dll.  This therefore ignores
            // desktop.ini files that contain links that look like this:
            // [.ShellClassInfo]
            // LocalizedResourceName=@%SystemRoot%\system32\shell32.dll,-21769
            // IconResource=%SystemRoot%\system32\imageres.dll,-183   
            // [LocalizedFileNames]
            // Remote Desktop Connection.lnk=@%SystemRoot%\system32\mstsc.exe,-4000
            // Virtual Windows XP.lnk=@%SystemRoot%\system32\VMCPropertyHandler.dll,-509
            //
            // kingw - 4 /10/11

            if (objIconPattern.IsMatch(content))
                return true;
            else
                return false;
		}

		private void Msg(string msg)
		{
			msgBox.AppendText(msg);
			msgBox.Select(msgBox.TextLength, 0);
			msgBox.ScrollToCaret();
		}

		private void createNewBtn_Click(object sender, EventArgs e)
		{
			try
			{
				Process.Start("VPCWizard.exe", "-type VM");
				LoadVPCs();
			}
			catch (Exception ex)
			{
				Msg("Error creating new VM: " + ex.Message + "\n");
			}
		}

		private void launchBtn_Click(object sender, EventArgs e)
		{
			if (vpcListView.SelectedItems.Count == 0)
				return;
			string vmc = vpcListView.SelectedItems[0].SubItems[3].Text;
			try
			{
				Process.Start("vmwindow.exe", "\"" + vmc + "\"");
			}
			catch (Exception ex)
			{
				Msg("Error launching VM: " + ex.Message + "\n");
			}
		}

		private void duplicateBtn_Click(object sender, EventArgs e)
		{
			if (vpcListView.SelectedIndices.Count == 0)
				return;
			int clicked = vpcListView.SelectedIndices[0];

			Msg("Duplicating " + m_vms[clicked].Name + " ... ");
			DuplicateForm dupForm = new DuplicateForm(m_vms[clicked]);
			dupForm.ShowDialog(this);
			if (dupForm.NewVM != "")
			{
				LoadVPCs();
				Msg("new VM created: " + dupForm.NewVM + "\n");
				for (int i = 0; i < m_vms.Length; ++i)
				{
					if (m_vms[i].Name == dupForm.NewVM)
					{
						vpcListView.Items[i].Selected = true;
						vpcListView.Select();
						break;
					}
				}
			}
			else
			{
				Msg("creation of new VM cancelled.\n");
			}
		}

		private void delVpcBtn_Click(object sender, EventArgs e)
		{
			if (vpcListView.SelectedIndices.Count == 0)
				return;
			int clicked = vpcListView.SelectedIndices[0];
			Msg("Deleting " + m_vms[clicked].Name + "... ");
			string verify = "Wow... are you sure you want to delete the '" +
				m_vms[clicked].Name + "' virtual machine?\n\nFiles to be deleted:\n" +
				m_vms[clicked].Vmcx + "\n" + m_vms[clicked].Vmc + "\n" +
				m_vms[clicked].Vhd + "\n";
			if (m_vms[clicked].Vsv.Length > 0)
				verify += m_vms[clicked].Vsv;
			if (MessageBox.Show(verify, "Delete VM",
				MessageBoxButtons.YesNo) == DialogResult.No)
			{
				Msg("Actually not. Cancelled.\n");
				return;
			}
			try
			{
				File.Delete(m_vms[clicked].Vhd);
				File.Delete(m_vms[clicked].Vmc);
				File.Delete(m_vms[clicked].Vmcx);
				File.Delete(m_vms[clicked].Vmc + ".vpcbackup");	// backup
				File.Delete(m_vms[clicked].Vmc.Replace(".vmc", ".vud"));	// undo
				if (m_vms[clicked].Vsv.Length > 0)	// saved state
					File.Delete(m_vms[clicked].Vsv);
				Msg("Done\n");
			}
			catch (Exception ex)
			{
				Msg("Failed: " + ex.Message + "\n");
			}
			LoadVPCs();
		}

		private void MainForm_SizeChanged(object sender, EventArgs e)
		{
			// Resize controls to the width of the form minus border
			vpcListView.Height = launchBtn.Top - vpcListView.Top -
				(seperatorGb.Top - launchBtn.Bottom);
            vpcListView.Width = (int)(Width - aboutLabel.Width);
			msgBox.Width = vpcListView.Width;
			seperatorGb.Width = vpcListView.Width;
			aboutLabel.Left = vpcListView.Right - aboutLabel.Width;
		}

		private void updateLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			Process.Start("http://xpmore.codeplex.com");
			updateLink.LinkVisited = true;
		}

		private void aboutLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			AboutForm about = new AboutForm();
			about.ShowDialog();
		}

		private void setDiffDiskBtn_Click(object sender, EventArgs e)
		{
			if (vpcListView.SelectedIndices.Count == 0)
				return;
			int clicked = vpcListView.SelectedIndices[0];
			// code below based on http://blogs.msdn.com/virtual_pc_guy/archive/2007/03/22/fixing-a-broken-differencing-disk-with-virtual-server-and-vbscript.aspx
			// note - MS ILMerge is used to embed the interop dll into the exe assembly,
			// so that the program remains absolutely portable
			VMVirtualPCClass vpc;
			try
			{
				vpc = new VMVirtualPCClass();
			}
			catch (COMException ex)
			{
				MessageBox.Show("Error operating the Virtual PC. " +
					"Are you sure it's installed?\nIf it is, please report this " +
					"problem to xpmore@live.com\n\nThe gory details:\n" + ex.Message,
					"Operation failed");
				return;
			}
			VMHardDisk clickedVhd = GetHardDisk(vpc, m_vms[clicked].Vhd);
			if (clickedVhd == null)
				return;	// bad vhd file
			if (clickedVhd.Type != VMHardDiskType.vmDiskType_Differencing)
			{
				MessageBox.Show("The selected VPC is not differencing, " +
					"and therefore cannot have a parent.");
				return;
			}
			Msg("Setting " + m_vms[clicked].Name + " differencing disk... ");

			// let the user select the new parent vhd
			string parentFile = GetParentVhdFile();
			if (parentFile.Length == 0)
			{
				Msg("Cancelled.\n");
				return;
			}

			// Create VHD object
			VMHardDisk parentVhd = GetHardDisk(vpc, parentFile);
			if (parentVhd == null)
			{
				Msg("Failed.\n");
				return;
			}

			// Merge the VHD to a new dynamic disk
			clickedVhd.Parent = parentVhd;
			Msg("New parent is " + parentFile + ".\n");
		}

		private string GetParentVhdFile()
		{
			OpenFileDialog browser = new OpenFileDialog();
			browser.Title = "Select the parent VHD";
			browser.Filter = "VHD files (*.vhd)|*.vhd|All files (*.*)|*.*";
			browser.FilterIndex = 1;
			if (browser.ShowDialog() != DialogResult.OK)
			{
				return "";
			}
			return browser.FileName;
		}

		private VMHardDisk GetHardDisk(VMVirtualPCClass vpc, string vhd)
		{
			try
			{
				VMHardDisk clickedVhd = vpc.GetHardDisk(vhd);
				return clickedVhd;
			}
			catch (Exception ex)
			{
				MessageBox.Show("VHD file could not be used: " + ex.Message,
					"Error opening VHD");
			}
			return null;
		}

		private void editVmBtn_Click(object sender, EventArgs e)
		{
			if (vpcListView.SelectedIndices.Count == 0)
				return;
			int clicked = vpcListView.SelectedIndices[0];
			try
			{
				Process.Start("vpcsettings.exe", "\"" + m_vms[clicked].Vmcx + "\"");
			}
			catch (Exception ex)
			{
				Msg("Error launching configuration: " + ex);
			}
		}

        private void vpcListView_SelectedIndexChanged(object sender, EventArgs e)
        {

        }
	}
}
